﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
using System.Linq;
using System.Text;

using Gnosis.Alexandria.Model.Commands;

namespace Gnosis.Alexandria.Model.Repositories
{
    public abstract class RepositoryBase
        : IRepository
    {
        #region Private Members

        private IDictionary<Type, ICommandBuilder> _defaultBuilders = new Dictionary<Type, ICommandBuilder>();

        private IDbConnection GetConnection()
        {
            return new SQLiteConnection(string.Format("Data Source={0}.db;Version=3;", Name));
        }

        private ICommandBuilder GetCommandBuilder(IIdentifiable record)
        {
            if (record.IsDeleted)
                return new DeleteTextBuilder(record);
            else if (record.IsNew)
                return new InsertTextBuilder(record);
            else if (record.IsChanged)
                return new UpdateTextBuilder(record);
            else
                return null;
        }

        private void ExecuteNonQuery(string text)
        {
            using (var connection = GetConnection())
            {
                connection.Open();
                var builder = new AdHocCommandBuilder(text);
                using (var command = builder.ToCommand(connection))
                {
                    command.ExecuteNonQuery();
                }
            }
        }

        private void ExecuteNonQuery(IIdentifiable record)
        {
            IDbTransaction transaction = null;

            var builder = GetCommandBuilder(record);

            if (builder != null)
            {
                using (var connection = GetConnection())
                {
                    try
                    {

                        connection.Open();
                        transaction = connection.BeginTransaction();
                        var command = builder.ToCommand(connection, transaction);
                        command.ExecuteNonQuery();

                        transaction.Commit();
                    }
                    catch (Exception)
                    {
                        if (transaction != null)
                            transaction.Rollback();

                        throw;
                    }
                }
            }
        }

        private void ExecuteNonQuery(IBatch batch)
        {
            IDbTransaction transaction = null;

            using (var connection = GetConnection())
            {
                try
                {
                    connection.Open();
                    transaction = connection.BeginTransaction();

                    foreach (var record in batch.Records)
                    {
                        var builder = GetCommandBuilder(record);
                        if (builder != null)
                        {
                            var command = builder.ToCommand(connection, transaction);
                            var value = command.ExecuteScalar();
                            batch.InvokeRecordCallback(record, value);
                        }
                    }

                    transaction.Commit();
                }
                catch (Exception)
                {
                    if (transaction != null)
                        transaction.Rollback();

                    throw;
                }
            }
        }

        private IDataReader GetDataReader(IDbConnection connection, ICommandBuilder builder)
        {
            var command = builder.ToCommand(connection);
            return command.ExecuteReader();
        }

        private static void InitializeRecord(IIdentifiable record, IDataReader reader)
        {
            var data = new Dictionary<string, object>();
            for (var i = 0; i < reader.FieldCount; i++)
            {
                var value = reader.IsDBNull(i) ? null : reader.GetValue(i);
                data.Add(reader.GetName(i), value);
            }
            record.Initialize(data);
        }

        #endregion

        #region Protected Members

        protected virtual void InitializeRepository() { }
        protected abstract string Name { get; }
        protected abstract string GetInitializeCommandText();
        protected abstract IFactory<T> GetFactory<T>() where T : IIdentifiable;
        protected abstract T GetCached<T>(long id) where T : IIdentifiable;
        protected abstract ICollection<T> GetCached<T>(ICommandBuilder builder) where T : IIdentifiable;

        protected void AddBuilder<T>(ICommandBuilder builder)
        {
            _defaultBuilders[typeof(T)] = builder;
        }

        protected T GetRecord<T>(long id)
            where T : IIdentifiable
        {
            var record = default(T);
                
            var factory = GetFactory<T>();
            if (factory != null)
            {
                record = factory.Create();

                var builder = new SelectTextBuilder(record, id);
                using (var connection = GetConnection())
                {
                    connection.Open();
                    using (var reader = GetDataReader(connection, builder))
                    {
                        if (reader.Read())
                        {
                            InitializeRecord(record, reader);
                        }
                    }
                }
            }

            return record;
        }

        protected ICollection<T> GetCollection<T>(ICommandBuilder builder)
            where T : IIdentifiable
        {
            IList<T> results = new List<T>();

            var factory = GetFactory<T>();
            if (factory != null)
            {
                using (var connection = GetConnection())
                {
                    connection.Open();
                    using (var reader = GetDataReader(connection, builder))
                    {
                        while (reader.Read())
                        {
                            var record = factory.Create();
                            InitializeRecord(record, reader);
                            results.Add(record);
                        }
                    }
                }
            }

            return results;
        }

        #endregion

        #region IRepository Members

        public void Initialize()
        {
            ExecuteNonQuery(GetInitializeCommandText());
            InitializeRepository();
        }

        public virtual T Get<T>(long id)
            where T : IIdentifiable
        {
            T record = GetCached<T>(id);
            if (record != null)
                return record;
          
            return GetRecord<T>(id);
        }

        public virtual ICollection<T> Get<T>(ICommandBuilder builder)
            where T : IIdentifiable
        {
            ICollection<T> results = new List<T>();

            results = GetCached<T>(builder);
            if (results != null)
                return results;

            return GetCollection<T>(builder);
        }

        public virtual ICollection<T> GetAll<T>()
            where T : IIdentifiable
        {
            ICollection<T> results = new List<T>();

            if (_defaultBuilders.ContainsKey(typeof(T)))
            {
                results = Get<T>(_defaultBuilders[typeof(T)]);
            }
            else
            {
                var factory = GetFactory<T>();
                if (factory != null)
                {
                    var record = factory.Create();
                    results = GetCollection<T>(new SelectTextBuilder(record));
                }
            }

            return results;
        }

        public virtual void Persist<T>(T record)
            where T : IIdentifiable
        {
            ExecuteNonQuery(record);
        }

        public virtual void Persist(IBatch batch)
        {
            ExecuteNonQuery(batch);
        }

        #endregion
    }
}
